
#ifndef	SSAO_QUALITY

float calc_hbao(float z, float4 curN, float2 tc0)
{
	return 1.0f;
}

#else	//	SSAO_QUALITY

#define g_Resolution 	screen_res.xy
#define g_InvResolution screen_res.zw


  const float g_R = 0.400009334f;
  const float g_sqr_R = 0.160007462f;
  const float g_inv_R = 2.49994159f;

// const float g_Contrast = 1.5f;
const float g_Contrast = 0.8f;
const float g_AngleBias = 0.0f;
  
#if SSAO_QUALITY == 3
const float g_NumDir = 6.0f;
const float g_NumSteps = 3.0f;
#elif SSAO_QUALITY == 2
const float g_NumDir = 5.0f;
const float g_NumSteps = 3.0f;
#elif SSAO_QUALITY == 1
const float g_NumDir = 4.0f;
const float g_NumSteps = 3.0f;
#endif

uniform sampler2D 	jitter4;

#define M_PI 3.14159265f

//----------------------------------------------------------------------------------
float tangent(float3 P, float3 S)
{
    return (P.z - S.z) / length(S.xy - P.xy);
}

//----------------------------------------------------------------------------------
float3 fetch_eye_pos(float2 uv)
{
#ifdef SSAO_OPT_DATA
    float z = tex2Dlod(s_half_depth, float4(uv, 0, 0)).x;
    return uv_to_eye(uv, z);
#else // SSAO_OPT_DATA 
    return tex2Dlod	(s_position, float4(uv, 0, 0));
#endif // SSAO_OPT_DATA
   
}

float3 tangent_eye_pos(float2 uv, float4 tangentPlane)
{
    // view vector going through the surface point at uv
    float3 V = fetch_eye_pos(uv);
    float NdotV = dot(tangentPlane.xyz, V);
    // intersect with tangent plane except for silhouette edges
    if (NdotV < 0.0) V *= (tangentPlane.w / NdotV);
    return V;
}


float length2(float3 v) { return dot(v, v); } 

//----------------------------------------------------------------------------------
float3 min_diff(float3 P, float3 Pr, float3 Pl)
{
    float3 V1 = Pr - P;
    float3 V2 = P - Pl;
    return (length2(V1) < length2(V2)) ? V1 : V2;
}

//----------------------------------------------------------------------------------
float falloff(float r)
{
    return 1.0f - r*r;
}

//----------------------------------------------------------------------------------
float2 snap_uv_offset(float2 uv)
{
    return round(uv * g_Resolution) * g_InvResolution;
}

//----------------------------------------------------------------------------------
float tan_to_sin(float x)
{
    return x / sqrt(1.0f + x*x);
}

//----------------------------------------------------------------------------------
float3 tangent_vector(float2 deltaUV, float3 dPdu, float3 dPdv)
{
    return deltaUV.x * dPdu + deltaUV.y * dPdv;
}

//----------------------------------------------------------------------------------
void integrate_direction(inout float ao, float3 P, float2 uv, float2 deltaUV,
                         float numSteps, float tanH, float sinH)
{
    for (float j = 1; j <= numSteps; ++j) {
        uv += deltaUV;
        float3 S = fetch_eye_pos(uv);
        
        // Ignore any samples outside the radius of influence
        float d2  = length2(S - P);
        if (d2 < g_sqr_R) {
            float tanS = tangent(P, S);

            //[branch]
            if(tanS > tanH) {
                // Accumulate AO between the horizon and the sample
                float sinS = tanS / sqrt(1.0f + tanS*tanS);
                float r = sqrt(d2) * g_inv_R;
                ao += falloff(r) * (sinS - sinH);
                
                // Update the current horizon angle
                tanH = tanS;
                sinH = sinS;
            }
        }
    }
}

//----------------------------------------------------------------------------------
float horizon_occlusion_integrateDirection(float2 deltaUV, 
                                           float2 uv0, 
                                           float3 P, 
                                           float numSteps, 
                                           float randstep)
{
    // Randomize starting point within the first sample distance
    float2 uv = uv0 + snap_uv_offset( randstep * deltaUV );
    
    // Snap increments to pixels to avoid disparities between xy 
    // and z sample locations and sample along a line
    deltaUV = snap_uv_offset( deltaUV );

    // Add a small bias in case (g_AngleBias == 0.0)
    float tanT = tan(-M_PI*0.5 + g_AngleBias + 1.e-5);
    float sinT = tan_to_sin(tanT);

    float ao = 0;
    integrate_direction(ao, P, uv, deltaUV, numSteps, tanT, sinT);

    // Integrate opposite directions together
    deltaUV = -deltaUV;
    uv = uv0 + snap_uv_offset( randstep * deltaUV );
    integrate_direction(ao, P, uv, deltaUV, numSteps, tanT, sinT);

    // Divide by 2 because we have integrated 2 directions together
    // Subtract 1 and clamp to remove the part below the surface
    return max(ao * 0.5 - 1.0, 0.0);
}

//----------------------------------------------------------------------------------
float horizon_occlusion(float2 deltaUV, 
                        float2 uv0, 
                        float3 P, 
                        float numSteps, 
                        float randstep,
                        float3 dPdu,
                        float3 dPdv)
{
    // Randomize starting point within the first sample distance
    float2 uv = uv0 + snap_uv_offset( randstep * deltaUV );
    
    // Snap increments to pixels to avoid disparities between xy 
    // and z sample locations and sample along a line
    deltaUV = snap_uv_offset( deltaUV );

    // Compute tangent vector using the tangent plane
    float3 T = deltaUV.x * dPdu + deltaUV.y * dPdv;

    float phi = atan(-T.z / length(T.xy)) + 0.1f;//g_AngleBias;
    float tanH = tan(min(phi, M_PI*0.5));
    float sinH = tanH / sqrt(1.0f + tanH*tanH);

    float ao = 0;
    for(float j = 1; j <= numSteps; ++j) {
    uv += deltaUV;
        float3 S = fetch_eye_pos(uv);
        
        // Ignore any samples outside the radius of influence
        float d2  = length2(S - P);
        if (d2 < g_sqr_R) {
            float tanS = tangent(P, S);

			//[branch]
            if(tanS > tanH) {
                // Accumulate AO between the horizon and the sample
                float sinS = tanS / sqrt(1.0f + tanS*tanS);
                float r = sqrt(d2) * g_inv_R;
                ao += falloff(r) * (sinS - sinH);
                
                // Update the current horizon angle
                tanH = tanS;
                sinH = sinS;
            }
        } 
    }

    return ao;
}

float calc_hbao(float z, float4 curN, float2 tc0)
{
	float3 N = curN.xyz;
    float3 P = uv_to_eye(tc0, z);

    // Calculate the real number of steps based on Z distance, and  
    // early out if geometry is too far away.   	

	float2 	step_size	= float2	(.5f / 1024.f, .5f / 768.f)*ssao_kernel_size/max(z,1.3);
    float numSteps = min ( g_NumSteps, min(step_size.x * g_Resolution.x, step_size.y * g_Resolution.y));
    float numDirs = min ( g_NumDir, min(step_size.x / 4 * g_Resolution.x, step_size.y / 4 * g_Resolution.y));
//    if( numSteps < 1.0 ) return 1.0;
    step_size = step_size / ( numSteps + 1 );


    float4 tangentPlane = float4(N, dot(P, N));
    
    // Nearest neighbor pixels on the tangent plane
    float3 Pr = tangent_eye_pos(tc0 + float2(g_InvResolution.x, 0), tangentPlane);
    float3 Pl = tangent_eye_pos(tc0 + float2(-g_InvResolution.x, 0), tangentPlane);
    float3 Pt = tangent_eye_pos(tc0 + float2(0, g_InvResolution.y), tangentPlane);
    float3 Pb = tangent_eye_pos(tc0 + float2(0, -g_InvResolution.y), tangentPlane);
    
    //float3 Pr, Pl, Pt, Pb;
    //Pr = fetch_eye_pos(IN.texUV + float2(g_InvResolution.x, 0));
    //Pl = fetch_eye_pos(IN.texUV + float2(-g_InvResolution.x, 0));
    //Pt = fetch_eye_pos(IN.texUV + float2(0, g_InvResolution.y));
    //Pb = fetch_eye_pos(IN.texUV + float2(0, -g_InvResolution.y));
    //float3 N = normalize(cross(Pr - Pl, Pt - Pb));
    //tangentPlane = float4(N, dot(P, N));
    
    // Screen-aligned basis for the tangent plane
    float3 dPdu = min_diff(P, Pr, Pl);
    float3 dPdv = min_diff(P, Pt, Pb) * (g_Resolution.y * g_InvResolution.x);

    // (cos(alpha),sin(alpha),jitter)
#ifndef HBAO_WORLD_JITTER
    float3 rand_Dir = tex2D(jitter4, tc0 * g_Resolution /64.0f).rgb;
#else
    float3 tc1	= mul( m_v2w, float4(P,1) );
    tc1 *= ssao_noise_tile_factor;
    tc1.xz += tc1.y; 
    float3 rand_Dir = tex2D(jitter4, tc1.xz).xyz; 
#endif
    //rand_Dir = float3(1,0,0);

    // Loop over all directions
    float ao = 0;
    float delta = g_NumDir / numDirs;
    float alpha = 2.0f * M_PI / g_NumDir;
    for (float d = 0; d < g_NumDir; d+=delta) {
        float angle = alpha * d;
        float2 dir = float2(cos(angle), sin(angle));
        float2 deltaUV = float2(dir.x*rand_Dir.x - dir.y*rand_Dir.y, 
                                dir.x*rand_Dir.y + dir.y*rand_Dir.x)
                        * step_size.xy;
        ao += horizon_occlusion(deltaUV, tc0, P, numSteps, rand_Dir.z, dPdu, dPdv);
	//ao += horizon_occlusion_integrateDirection(deltaUV, tc0, P, numSteps, rand_Dir.z);
    }

    // this saturate is not needed if the AO render target is UNORM
    return saturate(1.0 - ao / g_NumDir * g_Contrast);
}
#endif	//	SSAO_QUALITY